/**
 * Copyright (c) 2019 Coder League
 * All rights reserved.
 *
 * File：WeixinInterfaceService.java
 * History:
 *         2019年5月26日: Initially created, Chrise.
 */
package club.coderleague.ilsp.service.interfaces;

import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.wxpay.sdk.WXPayUtil;

import club.coderleague.ilsp.cache.CacheManager;
import club.coderleague.ilsp.common.config.properties.WeixinSettings;
import club.coderleague.ilsp.common.domain.beans.PayOrder;
import club.coderleague.ilsp.common.domain.beans.PaymentInterfaceResponse;
import club.coderleague.ilsp.common.domain.beans.RefundOrder;
import club.coderleague.ilsp.common.domain.beans.SystemConfig;
import club.coderleague.ilsp.common.domain.beans.UserSession;
import club.coderleague.ilsp.common.domain.beans.wxmp.AbstractMessage;
import club.coderleague.ilsp.common.domain.enums.OrderPaymentMode;
import club.coderleague.ilsp.common.domain.enums.OrderPaymentState;
import club.coderleague.ilsp.entities.Orders;
import club.coderleague.ilsp.handler.wxmp.MessageHandler;
import club.coderleague.ilsp.service.orders.OrdersService;
import club.coderleague.ilsp.util.CommonUtil;
import club.coderleague.ilsp.util.HttpUtil;
import club.coderleague.ilsp.util.StreamUtil;
import club.coderleague.security.AlgorithmBeanFactory;
import club.coderleague.security.algorithm.AESEncryptor;
import club.coderleague.security.algorithm.MD5Signer;
import club.coderleague.security.algorithm.SHA1Signer;
import club.coderleague.security.support.CiphertextMode;

/**
 * 微信接口服务。
 * @author Chrise
 */
@Service
public class WeixinInterfaceService extends AbstractInterfaceService {
	private static final Logger LOGGER = LoggerFactory.getLogger(WeixinInterfaceService.class);
	private static final MD5Signer SIGNER = AlgorithmBeanFactory.getAlgorithmBean(MD5Signer.class);
	private static final AESEncryptor ENCRYPTOR = AlgorithmBeanFactory.getAlgorithmBean(AESEncryptor.class);
	private static final String CHARSET = "UTF-8";
	private static final String SIGN_TYPE = "MD5";
	private static final String TRADE_TYPE = "JSAPI";
	private static final String TRADE_STATUS_SUCCESS = "SUCCESS";
	private static final String TRADE_MESSAGE_OK = "OK";
	private static final String AMOUNT_FORMAT = "%.2f";
	private static final String PACKAGE_FORMAT = "prepay_id=%s";
	private static final String TIMEOUT_DELIMITER = "-";
	private static final String KEY_APPID = "appid";
	private static final String KEY_MCH_ID = "mch_id";
	private static final String KEY_NONCE_STR = "nonce_str";
	private static final String KEY_SIGN = "sign";
	private static final String KEY_BODY = "body";
	private static final String KEY_OUT_TRADE_NO = "out_trade_no";
	private static final String KEY_TOTAL_FEE = "total_fee";
	private static final String KEY_SPBILL_CREATE_IP = "spbill_create_ip";
	private static final String KEY_NOTIFY_URL = "notify_url";
	private static final String KEY_TRADE_TYPE = "trade_type";
	private static final String KEY_OPENID = "openid";
	private static final String KEY_RETURN_CODE = "return_code";
	private static final String KEY_RETURN_MSG = "return_msg";
	private static final String KEY_RESULT_CODE = "result_code";
	private static final String KEY_PREPAY_ID = "prepay_id";
	private static final String KEY_TRADE_STATE = "trade_state";
	private static final String KEY_TRANSACTION_ID = "transaction_id";
	private static final String KEY_TIME_END = "time_end";
	private static final String KEY_OUT_REFUND_NO = "out_refund_no";
	private static final String KEY_REFUND_FEE = "refund_fee";
	private static final String KEY_REQ_INFO = "req_info";
	private static final String KEY_REFUND_STATUS = "refund_status";
	private static final String KEY_SUCCESS_TIME = "success_time";
	private static final String KEY_OUT_REFUND_NO_0 = "out_refund_no_0";
	private static final String KEY_REFUND_STATUS_0 = "refund_status_0";
	private static final String KEY_REFUND_SUCCESS_TIME_0 = "refund_success_time_0";
	private static final String KEY_JSAPI_APPID = "appId";
	private static final String KEY_JSAPI_TIMESTAMP = "timeStamp";
	private static final String KEY_JSAPI_NONCESTR = "nonceStr";
	private static final String KEY_JSAPI_PACKAGE = "package";
	private static final String KEY_JSAPI_SIGNTYPE = "signType";
	private static final String KEY_JSAPI_PAYSIGN = "paySign";
	private static final String DATE_FORMAT = "yyyyMMddHHmmss";
	private static final String REFUND_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
	private static String notifyResponse = "";
	
	@Value("${custom.resource.root.real}")
	private String rootReal;
	
	@Autowired
	private WeixinSettings wxSettings;
	@Autowired
	private CacheManager cacheManager;
	@Autowired
	private OrdersService ordersService;
	private Map<String, MessageHandler> messageHandlers = new ConcurrentHashMap<String, MessageHandler>();
	
	static {
		try {
			Map<String, String> res = new HashMap<String, String>();
			res.put(KEY_RETURN_CODE, TRADE_STATUS_SUCCESS);
			res.put(KEY_RETURN_MSG, TRADE_MESSAGE_OK);
			notifyResponse = WXPayUtil.mapToXml(res);
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#queryTrade(club.coderleague.ilsp.entities.Orders)
	 */
	@Override
	public PaymentInterfaceResponse queryTrade(Orders order) throws Exception {
		// 检查支付状态
		if (OrderPaymentState.PAID.equalsValue(order.getPaymentstate())) 
			return new PaymentInterfaceResponse(order.getEntityid(), order.getPaymenttradeno(), order.getPaytime());
		
		// 启用本地支付
		if (this.wxSettings.getLocalPayEnabled()) 
			return new PaymentInterfaceResponse(order.getEntityid(), String.valueOf(order.getEntityid()), new Date());
		
		// 获取系统配置
		SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
		if (sc == null || !sc.getWxswitch()) throw new RuntimeException("The system config not found or weixin api not available.");
		
		// 构造请求参数
		Map<String, String> params = new HashMap<String, String>();
		params.put(KEY_APPID, sc.getWxappid());
		params.put(KEY_MCH_ID, sc.getWxmerid());
		params.put(KEY_OUT_TRADE_NO, String.valueOf(order.getEntityid()));
		params.put(KEY_NONCE_STR, WXPayUtil.generateNonceStr());
		
		// 签名加入请求参数
		String sign = WXPayUtil.generateSignature(params, sc.getWxapikey());
		params.put(KEY_SIGN, sign);
		
		// 生成请求内容
		String content = WXPayUtil.mapToXml(params);
		
		// 执行请求并验证结果
		String xml = HttpUtil.post(this.wxSettings.getPayQueryApi(), content);
		Map<String, String> response = WXPayUtil.xmlToMap(xml);
		if (TRADE_STATUS_SUCCESS.equals(response.get(KEY_RETURN_CODE)) && 
			TRADE_STATUS_SUCCESS.equals(response.get(KEY_RESULT_CODE))) {
			if (WXPayUtil.isSignatureValid(response, sc.getWxapikey())) {	// 验签成功
				// 支付成功
				if (TRADE_STATUS_SUCCESS.equals(response.get(KEY_TRADE_STATE))) {
					// 解析付款时间
					Date time = new SimpleDateFormat(DATE_FORMAT).parse(response.get(KEY_TIME_END));
					
					return new PaymentInterfaceResponse(order.getEntityid(), response.get(KEY_TRANSACTION_ID), time);
				}
			} else throw new RuntimeException("The signature verify failed. ->" + xml);
		} else throw new RuntimeException("The query return failed. -> " + xml);
		
		return null;
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#applyRefund(club.coderleague.ilsp.common.domain.beans.RefundOrder)
	 */
	@Override
	public void applyRefund(RefundOrder order) throws Exception {
		// 验证退款订单
		if (!verifyRefundOrder(order)) throw new RuntimeException("The order is invalid.");
		
		// 获取系统配置
		SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
		if (sc == null || !sc.getWxswitch()) throw new RuntimeException("The system config not found or weixin api not available.");
		
		// 构造请求参数
		Map<String, String> params = new HashMap<String, String>();
		params.put(KEY_APPID, sc.getWxappid());
		params.put(KEY_MCH_ID, sc.getWxmerid());
		params.put(KEY_NONCE_STR, WXPayUtil.generateNonceStr());
		params.put(KEY_OUT_TRADE_NO, String.valueOf(order.getOrderid()));
		params.put(KEY_OUT_REFUND_NO, order.getRefundtradeno());
		params.put(KEY_TOTAL_FEE, String.valueOf(order.getPaymenttotal()));
		params.put(KEY_REFUND_FEE, String.valueOf(order.getRefundtotal()));
		params.put(KEY_NOTIFY_URL, this.wxSettings.getRefundNotifyUrl());
		
		// 签名加入请求参数
		String sign = WXPayUtil.generateSignature(params, sc.getWxapikey());
		params.put(KEY_SIGN, sign);
		
		// 生成请求内容
		String content = WXPayUtil.mapToXml(params);
		
		if (this.wxSettings.getLocalPayEnabled()) {
			// 启动通知发送任务
			startRefundNotifySendTask(this.wxSettings.getRefundNotifyUrl(), order.getOrderid(), order.getRefundtradeno());
		} else {
			// 执行请求并验证结果
			String cert = this.rootReal + "/" + sc.getWxcerfile();
			String xml = HttpUtil.post(this.wxSettings.getRefundApi(), content, cert, sc.getWxmerid());
			Map<String, String> response = WXPayUtil.xmlToMap(xml);
			if (TRADE_STATUS_SUCCESS.equals(response.get(KEY_RETURN_CODE)) && 
				TRADE_STATUS_SUCCESS.equals(response.get(KEY_RESULT_CODE))) {
				if (WXPayUtil.isSignatureValid(response, sc.getWxapikey())) return;		// 验签成功
				else LOGGER.error("The signature verify failed for [{}]", response);
			} else LOGGER.error("Apply refund with weixin returns [{}]", xml);
			throw new RuntimeException("Apply refund with weixin failed.");
		}
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#queryRefund(club.coderleague.ilsp.entities.Orders)
	 */
	@Override
	public PaymentInterfaceResponse queryRefund(Orders order) throws Exception {
		// 检查支付状态
		if (OrderPaymentState.REFUNDED.equalsValue(order.getPaymentstate())) 
			return new PaymentInterfaceResponse(order.getEntityid(), order.getRefundtradeno(), order.getRefundtime());
		
		// 启用本地支付
		if (this.wxSettings.getLocalPayEnabled()) 
			return new PaymentInterfaceResponse(order.getEntityid(), order.getRefundtradeno(), new Date());
		
		// 获取系统配置
		SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
		if (sc == null || !sc.getWxswitch()) throw new RuntimeException("The system config not found or weixin api not available.");
		
		// 构造请求参数
		Map<String, String> params = new HashMap<String, String>();
		params.put(KEY_APPID, sc.getWxappid());
		params.put(KEY_MCH_ID, sc.getWxmerid());
		params.put(KEY_NONCE_STR, WXPayUtil.generateNonceStr());
		params.put(KEY_OUT_REFUND_NO, order.getRefundtradeno());
		
		// 签名加入请求参数
		String sign = WXPayUtil.generateSignature(params, sc.getWxapikey());
		params.put(KEY_SIGN, sign);
		
		// 生成请求内容
		String content = WXPayUtil.mapToXml(params);
		
		// 执行请求并验证结果
		String xml = HttpUtil.post(this.wxSettings.getRefundQueryApi(), content);
		Map<String, String> response = WXPayUtil.xmlToMap(xml);
		if (TRADE_STATUS_SUCCESS.equals(response.get(KEY_RETURN_CODE)) && 
			TRADE_STATUS_SUCCESS.equals(response.get(KEY_RESULT_CODE))) {
			if (WXPayUtil.isSignatureValid(response, sc.getWxapikey())) {	// 验签成功
				if (TRADE_STATUS_SUCCESS.equals(response.get(KEY_REFUND_STATUS_0))) {
					// 解析退款时间
					Date time = new SimpleDateFormat(REFUND_DATE_FORMAT).parse(response.get(KEY_REFUND_SUCCESS_TIME_0));
					
					return new PaymentInterfaceResponse(Long.valueOf(response.get(KEY_OUT_TRADE_NO)), response.get(KEY_OUT_REFUND_NO_0), time);
				} else throw new RuntimeException("The refund not success. ->" + xml);
			} else throw new RuntimeException("The signature verify failed. ->" + xml);
		} else throw new RuntimeException("The query return failed. -> " + xml);
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#getDomain()
	 */
	@Override
	protected String getDomain() {
		return this.wxSettings.getDomain();
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#isWebAuthSimulatorEnabled()
	 */
	@Override
	protected boolean isWebAuthSimulatorEnabled() {
		return this.wxSettings.getWebAuthSimulatorEnabled();
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#constructRedirectUrl(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	protected String constructRedirectUrl(String redirect, String code, String state) {
		String url = redirect + "?code=" + code + "&state=" + state;
		return url;
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#constructOpenIdResponse(java.util.Map, java.lang.String)
	 */
	@Override
	protected void constructOpenIdResponse(Map<String, Object> response, String openId) {
		response.put("openid", openId);
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#sendPayNotify(java.lang.String, java.lang.String)
	 */
	@Override
	protected void sendPayNotify(String url, String order) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) {
				LOGGER.error("The system config not found or weixin api not available with [{}].", sc);
				return;
			}
			
			// 构造参数集合
			Map<String, String> params = new HashMap<String, String>();
			params.put(KEY_RETURN_CODE, TRADE_STATUS_SUCCESS);
			params.put(KEY_RESULT_CODE, TRADE_STATUS_SUCCESS);
			params.put(KEY_TRANSACTION_ID, order);
			params.put(KEY_OUT_TRADE_NO, order);
			params.put(KEY_TIME_END, new SimpleDateFormat(DATE_FORMAT).format(new Date()));
			
			// 签名加入参数集合
			String sign = WXPayUtil.generateSignature(params, sc.getWxapikey());
			params.put(KEY_SIGN, sign);
			
			String result = HttpUtil.post(url, WXPayUtil.mapToXml(params));
			LOGGER.info("The pay notify send success and result is [{}].", result);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#sendRefundNotify(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	protected void sendRefundNotify(String url, String order, String refund) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) {
				LOGGER.error("The system config not found or weixin api not available with [{}].", sc);
				return;
			}
			
			// 构造退款结果集合
			Map<String, String> map = new HashMap<String, String>();
			map.put(KEY_OUT_TRADE_NO, order);
			map.put(KEY_OUT_REFUND_NO, refund);
			map.put(KEY_REFUND_STATUS, TRADE_STATUS_SUCCESS);
			map.put(KEY_SUCCESS_TIME, new SimpleDateFormat(REFUND_DATE_FORMAT).format(new Date()));
			
			// 加密退款结果
			String content = WXPayUtil.mapToXml(map);
			String password = SIGNER.sign(sc.getWxapikey());
			String reqinfo = ENCRYPTOR.encrypt(content, password, CiphertextMode.BASE64);
			
			// 构造参数集合
			Map<String, String> params = new HashMap<String, String>();
			params.put(KEY_RETURN_CODE, TRADE_STATUS_SUCCESS);
			params.put(KEY_REQ_INFO, reqinfo);
			
			String result = HttpUtil.post(url, WXPayUtil.mapToXml(params));
			LOGGER.info("The refund notify send success and result is [{}].", result);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 添加消息处理器。
	 * @author Chrise 2019年5月31日
	 * @param handler 处理器对象。
	 */
	public void addMessageHandler(MessageHandler handler) {
		this.messageHandlers.put(handler.type(), handler);
	}
	
	/**
	 * 验证开发者身份。
	 * @author Chrise 2019年5月29日
	 * @param signature 签名。
	 * @param timestamp 时间戳。
	 * @param nonce 随机数。
	 * @param echostr 随机字符串。
	 * @param response 响应对象。
	 */
	public void verifyDeveloperIdentity(String signature, String timestamp, String nonce, String echostr, HttpServletResponse response) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) return;
			
			// 排序并连接验证参数
			String[] array = {sc.getWxvertoken(), timestamp, nonce};
			Arrays.sort(array);
			StringBuilder content = new StringBuilder();
			for (String str : array) {
				content.append(str);
			}
			
			// 验证签名
			String local = AlgorithmBeanFactory.getAlgorithmBean(SHA1Signer.class).sign(content.toString());
			if (local.equals(signature)) {
				// 原样返回随机参数
				response.getWriter().print(echostr);
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 分发消息。
	 * @author Chrise 2019年5月30日
	 * @param request 请求对象。
	 * @param response 响应对象。
	 */
	public void dispatchMessage(HttpServletRequest request, HttpServletResponse response) {
		try {
			// 设置编码
			request.setCharacterEncoding(CHARSET);
			response.setCharacterEncoding(CHARSET);
			
			// 解析消息
			AbstractMessage msg = AbstractMessage.parse(request.getInputStream());
			if (msg == null) {
				LOGGER.error("The message receive correctly but could not be parsed. -> [{}]", msg);
				response.getWriter().write("");
				return;
			}
			
			// 查找消息处理器
			MessageHandler handler = this.messageHandlers.get(msg.getMsgType());
			if (handler == null) {
				LOGGER.error("The message receive correctly but handler not found. -> [{}]", msg);
				response.getWriter().write("");
				return;
			}
			
			// 处理消息
			String reply = handler.handle(msg);
			if (reply == null) {	// 系统无法立即返回处理结果
				LOGGER.error("The message handle correctly but could not be replied immediately. -> [{}]", msg);
				reply = "";
			}
			
			// 回复处理结果
			response.getWriter().write(reply);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 获取授权地址。
	 * @author Chrise 2019年5月27日
	 * @param session 会话对象。
	 * @return 授权地址。
	 */
	public String getAuthUrl(HttpSession session) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) return "";
			
			// 缓存时间戳作为校验码
			long timestamp = System.currentTimeMillis();
			session.setAttribute(AUTH_STATE_KEY, timestamp);
			
			cacheAuthCode();	// 缓存授权代码
			
			// 构建授权地址
			String redirect = URLEncoder.encode(this.wxSettings.getWebAuthCallbackRequest(), "UTF-8");
			String url = String.format(this.wxSettings.getWebAuthCodeRequest(), sc.getWxappid(), redirect, timestamp);
			
			return url;
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		
		return null;
	}
	
	/**
	 * 获取开放标识。
	 * @author Chrise 2019年5月28日
	 * @param code 授权代码。
	 * @param state 状态。
	 * @param session 会话对象。
	 * @return 开放标识。
	 */
	@SuppressWarnings("unchecked")
	public String getOpenId(String code, String state, HttpSession session) {
		Object timestamp = null;
		
		try {
			// 验证授权校验码
			timestamp = session.getAttribute(AUTH_STATE_KEY);
			if (timestamp == null || 
				!timestamp.toString().equals(state) || 
				(System.currentTimeMillis() - ((Long)timestamp).longValue()) > this.wxSettings.getWebAuthCallbackTimeout()) return null;
			
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) return null;
			
			// 请求开放标识
			String url = String.format(this.wxSettings.getWebAuthTokenRequest(), sc.getWxappid(), sc.getWxappkey(), code);
			String result = HttpUtil.get(url);
			if (result != null) {
				// 解析并验证请求结果
				Map<String, Object> map = new ObjectMapper().readValue(result, Map.class);
				if (map.containsKey("openid")) return map.get("openid").toString();
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			if (timestamp != null) session.removeAttribute(AUTH_STATE_KEY);
		}
		
		return null;
	}
	
	/**
	 * 创建交易。
	 * @author Chrise 2019年6月23日
	 * @param order 订单标识。
	 * @param trade 交易号。
	 * @param user 用户会话。
	 * @param request 请求对象。
	 * @return 整体成功时返回交易创建结果，交易创建成功保存失败时返回交易号，发生异常时返回空字符串，订单无效时返回null。
	 */
	public Object createTrade(String order, String trade, UserSession user, HttpServletRequest request) {
		Map<String, String> result = new HashMap<String, String>();
		
		try {
			// 交易已创建未保存
			if (CommonUtil.isNotEmpty(trade)) {
				// 检查预支付交易标识是否失效
				String[] array = trade.split(TIMEOUT_DELIMITER);
				if ((Long.valueOf(array[1]) + this.wxSettings.getPrepayTimeout() * 1000L) > System.currentTimeMillis()) {
					// 保存交易
					if (!this.ordersService.savePayTrade(Long.valueOf(order), OrderPaymentMode.WECHAT, array[0])) return "";
				}
			}
			
			// 获取支付订单
			PayOrder po = getPayOrder(Long.valueOf(order));
			if (po == null) return null;
			
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) return "";
			
			// 交易已创建
			if (OrderPaymentMode.WECHAT.equalsValue(po.getPaymentmode()) && 
				CommonUtil.isNotEmpty(po.getPaymenttradeno()) && 
				(po.getTradecreatetime().getTime() + this.wxSettings.getPrepayTimeout() * 1000L) > System.currentTimeMillis()) {
				// 构造交易创建结果
				if (this.wxSettings.getLocalPayEnabled()) {
					// 启动通知发送任务
					startPayNotifySendTask(this.wxSettings.getNotifyUrl(), po.getOrderid());
					
					result.put(KEY_PREPAY_ID, String.valueOf(false));
				} else {
					// 支付参数加入交易创建结果
					this.addPaymentParams(po.getPaymenttradeno(), sc, result);
					
					result.put(KEY_PREPAY_ID, po.getPaymenttradeno());
				}
				result.put(KEY_PAY_API, OrderPaymentMode.WECHAT.getKey());
				result.put(KEY_TOTAL_FEE, String.format(AMOUNT_FORMAT, po.getPaymenttotal()));
				
				return result;
			}
			
			// 构造请求参数
			Map<String, String> params = new HashMap<String, String>();
			params.put(KEY_APPID, sc.getWxappid());
			params.put(KEY_MCH_ID, sc.getWxmerid());
			params.put(KEY_NONCE_STR, WXPayUtil.generateNonceStr());
			params.put(KEY_BODY, po.getOrdername());
			params.put(KEY_OUT_TRADE_NO, String.valueOf(po.getOrderid()));
			params.put(KEY_TOTAL_FEE, String.valueOf(po.getPaymenttotal()));
			params.put(KEY_SPBILL_CREATE_IP, this.getClientAddr(request));
			params.put(KEY_NOTIFY_URL, this.wxSettings.getNotifyUrl());
			params.put(KEY_TRADE_TYPE, TRADE_TYPE);
			params.put(KEY_OPENID, user.getOpenid());
			
			// 签名加入请求参数
			String sign = WXPayUtil.generateSignature(params, sc.getWxapikey());
			params.put(KEY_SIGN, sign);
			
			// 生成请求内容
			String content = WXPayUtil.mapToXml(params);
			
			if (this.wxSettings.getLocalPayEnabled()) {
				String tradeNo = String.valueOf(false);
				
				// 保存交易
				if (!this.ordersService.savePayTrade(po.getOrderid(), OrderPaymentMode.WECHAT, tradeNo)) 
					return tradeNo + TIMEOUT_DELIMITER + String.valueOf(System.currentTimeMillis());
				
				// 启动通知发送任务
				startPayNotifySendTask(this.wxSettings.getNotifyUrl(), po.getOrderid());
				
				// 构造交易创建结果
				result.put(KEY_PAY_API, OrderPaymentMode.WECHAT.getKey());
				result.put(KEY_PREPAY_ID, tradeNo);
				result.put(KEY_TOTAL_FEE, String.format(AMOUNT_FORMAT, po.getPaymenttotal()));
				
				return result;
			} else {
				// 执行请求并验证结果
				String xml = HttpUtil.post(this.wxSettings.getPayApi(), content);
				Map<String, String> response = WXPayUtil.xmlToMap(xml);
				if (TRADE_STATUS_SUCCESS.equals(response.get(KEY_RETURN_CODE)) && 
					TRADE_STATUS_SUCCESS.equals(response.get(KEY_RESULT_CODE))) {
					if (WXPayUtil.isSignatureValid(response, sc.getWxapikey())) {	// 验签成功
						String tradeNo = response.get(KEY_PREPAY_ID);
						
						// 保存交易
						if (!this.ordersService.savePayTrade(po.getOrderid(), OrderPaymentMode.WECHAT, tradeNo)) 
							return tradeNo + TIMEOUT_DELIMITER + String.valueOf(System.currentTimeMillis());
						
						// 构造交易创建结果
						this.addPaymentParams(tradeNo, sc, result);
						result.put(KEY_PAY_API, OrderPaymentMode.WECHAT.getKey());
						result.put(KEY_PREPAY_ID, tradeNo);
						result.put(KEY_TOTAL_FEE, String.format(AMOUNT_FORMAT, po.getPaymenttotal()));
						
						return result;
					} else LOGGER.error("The signature verify failed for [{}]", response);
				} else LOGGER.error("Create trade with weixin returns [{}]", xml);
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		
		return "";
	}
	
	/**
	 * 处理交易通知。
	 * @author Chrise 2019年6月25日
	 * @param request 请求对象。
	 * @param response 响应对象。
	 */
	public void handleTradeNotify(HttpServletRequest request, HttpServletResponse response) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) {
				response.getWriter().write(notifyResponse);	// 通知正确处理
				return;
			}
			
			// 构造参数集合
			String xml = StreamUtil.readStringFromStream(request.getInputStream(), CHARSET);
			Map<String, String> params = WXPayUtil.xmlToMap(xml);
			
			// 验证交易结果
			if (TRADE_STATUS_SUCCESS.equals(params.get(KEY_RETURN_CODE)) && 
				TRADE_STATUS_SUCCESS.equals(params.get(KEY_RESULT_CODE))) {
				if (WXPayUtil.isSignatureValid(params, sc.getWxapikey())) {		// 验签成功
					Long order = Long.valueOf(params.get(KEY_OUT_TRADE_NO));
					String trade = params.get(KEY_TRANSACTION_ID);
					
					try {
						// 解析付款时间
						Date time = new SimpleDateFormat(DATE_FORMAT).parse(params.get(KEY_TIME_END));
						
						// 通知支付成功
						PaymentInterfaceResponse pir = new PaymentInterfaceResponse(order, trade, time);
						this.ordersService.updateCompletePayment(pir);
					} catch (Exception e) {
						LOGGER.error("The payment complete handle occur exception -> " + e.getMessage(), e);
					}
				} else {
					LOGGER.error("The signature verify failed for [{}]", xml);
					return;
				}
			} else LOGGER.error("The trade not success with response [{}].", xml);
			
			// 通知正确处理
			response.getWriter().write(notifyResponse);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 处理交易通知。
	 * @author Chrise 2019年8月7日
	 * @param request 请求对象。
	 * @param response 响应对象。
	 */
	public void handleRefundNotify(HttpServletRequest request, HttpServletResponse response) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getWxswitch()) {
				response.getWriter().write(notifyResponse);	// 通知正确处理
				return;
			}
			
			// 构造参数集合
			String xml = StreamUtil.readStringFromStream(request.getInputStream(), CHARSET);
			Map<String, String> params = WXPayUtil.xmlToMap(xml);
			
			// 验证通知状态
			if (TRADE_STATUS_SUCCESS.equals(params.get(KEY_RETURN_CODE))) {
				// 解密退款结果
				String password = SIGNER.sign(sc.getWxapikey());
				String result = ENCRYPTOR.decrypt(params.get(KEY_REQ_INFO), password, CiphertextMode.BASE64);
				
				// 验证退款结果
				Map<String, String> map = WXPayUtil.xmlToMap(result);
				if (TRADE_STATUS_SUCCESS.equals(map.get(KEY_REFUND_STATUS))) {
					Long order = Long.valueOf(map.get(KEY_OUT_TRADE_NO));
					String trade = map.get(KEY_OUT_REFUND_NO);
					
					try {
						// 解析退款时间
						Date time = new SimpleDateFormat(REFUND_DATE_FORMAT).parse(map.get(KEY_SUCCESS_TIME));
						
						// 通知退款成功
						PaymentInterfaceResponse pir = new PaymentInterfaceResponse(order, trade, time);
						this.ordersService.updateCompleteRefund(pir);
					} catch (Exception e) {
						LOGGER.error("The refund complete handle occur exception -> " + e.getMessage(), e);
					}
				} else LOGGER.error("The refund not success with result [{}].", result);
			} else LOGGER.error("The refund not success with response [{}].", xml);
			
			// 通知正确处理
			response.getWriter().write(notifyResponse);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 获取客户端地址。
	 * @author Chrise 2019年6月13日
	 * @param request 请求对象。
	 * @return 客户端地址。
	 */
	private String getClientAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forarded-for");
		if (ip == null || "".equals(ip.trim()) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || "".equals(ip.trim()) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || "".equals(ip.trim()) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		
		if (ip.contains(",")) {
			String[] arr = ip.split(",");
			for (String s : arr) {
				if (!"unknown".equalsIgnoreCase(s)) {
					ip = s;
					break;
				}
			}
		}
		return ip;
	}
	
	/**
	 * 添加支付参数。
	 * @author Chrise 2019年6月23日
	 * @param trade 交易号。
	 * @param config 系统配置。
	 * @param result 交易创建结果集合。
	 * @throws Exception 添加参数错误。
	 */
	private void addPaymentParams(String trade, SystemConfig config, Map<String, String> result) throws Exception {
		// 支付参数加入交易创建结果
		result.put(KEY_JSAPI_APPID, config.getWxappid());
		result.put(KEY_JSAPI_TIMESTAMP, String.valueOf(WXPayUtil.getCurrentTimestamp()));
		result.put(KEY_JSAPI_NONCESTR, WXPayUtil.generateNonceStr());
		result.put(KEY_JSAPI_PACKAGE, String.format(PACKAGE_FORMAT, trade));
		result.put(KEY_JSAPI_SIGNTYPE, SIGN_TYPE);
		
		// 支付签名加入交易创建结果
		String paySign = WXPayUtil.generateSignature(result, config.getWxapikey());
		result.put(KEY_JSAPI_PAYSIGN, paySign);
	}
}
